Plotting in 3D has many useful applications. This notebook will illustrate some techniques used to create 3D plots in R. There are numerous resources that can be used for this purpose.

Preliminaries

Load the packages used in this notebook. packr contains a sample data set, tidyverse is a collection of packages for data manipulation and visualization, plot3D is a package for creating 3D plots, plot3Drgl is a package to convert plot3D static plots into interactive plots, and plotly is a package for creating interactive plots:

library(packr)
library(tidyverse)
library(plot3D)
library(plotly)

Summon the data set:

data(energy_and_emissions)

Quickly review the contents of the data set:

summary(energy_and_emissions)
   Country               Area            Population                   PYear         GDPPC            bblpd           EYear    
 Length:188         Min.   :      54   Min.   :5.292e+03   JULY 2017 EST.:188   Min.   :   145   Min.   :     400   2014:148  
 Class :character   1st Qu.:   25618   1st Qu.:1.955e+06                        1st Qu.:  1817   1st Qu.:   13000   2015: 34  
 Mode  :character   Median :  113098   Median :8.101e+06                        Median :  5620   Median :   53000   2016:  6  
                    Mean   :  666756   Mean   :3.847e+07                        Mean   : 13572   Mean   :  490635             
                    3rd Qu.:  479278   3rd Qu.:2.558e+07                        3rd Qu.: 16148   3rd Qu.:  255500             
                    Max.   :16377742   Max.   :1.379e+09                        Max.   :100161   Max.   :19530000             
    CO2_1995          CO2_2005          CO2_2015           Continent 
 Min.   :     12   Min.   :     14   Min.   :      28   Africa  :51  
 1st Qu.:    936   1st Qu.:   1379   1st Qu.:    2153   Americas:41  
 Median :   6661   Median :   8434   Median :   10062   Asia    :47  
 Mean   : 121256   Mean   : 152549   Mean   :  184978   Europe  :37  
 3rd Qu.:  61534   3rd Qu.:  62283   3rd Qu.:   75294   Oceania :12  
 Max.   :5294648   Max.   :6174717   Max.   :10641789                

Some variables are absolute values (e.g., population) and others are rates (e.g., GDP per capita). Mutate the dataframe to obain variables for energy consumption and emissions that are rates:

energy_and_emissions <- energy_and_emissions %>%
  mutate(GDPPC = GDPPC / 100000,
         EPC = bblpd/Population, 
         CO2PC_1995 = CO2_1995/Population, 
         CO2PC_2005 = CO2_2005/Population, 
         CO2PC_2015 = CO2_2015/Population)

Pseudo-3D plots

The first thing to note is that any plots rendered on a flat surface such as a page in a book or a computer screen are pseudo-3D in the sense that they only exist in 2D. That said, there are different ways of representing 3 and even higher dimensions in a 2D surface.

We will begin with some plots that are more pseudo-3D than others.

Perhaps the simplest way to represent a 3D plot in two dimensions is by projecting one of the dimensions on the other two, effectively flattening it but in such a way that an aspect of the 3D is still legible from the plot.

Consider the energy_and_emissions data set. It contains several variables, including population GDPPC, oil consumption in barrels of oil per day, and \(CO_2\) emissions.

ggplot(data = energy_and_emissions, aes(x = GDPPC, y = CO2PC_1995)) + geom_point()

ggplot(data = energy_and_emissions, aes(x = EPC, y = CO2PC_1995)) + geom_point()

ggplot(data = energy_and_emissions, aes(x = GDPPC, y = EPC, color = CO2PC_1995)) + 
  geom_point() + scale_color_viridis_c(direction = -1)

In this plot, we “flatten” the variable for emissions, projecting it on the 2D plane of GDP per capita and energy consumptions per capita, and represent the “height” by means of colors. We can further emphasize the “height” of the emissions variable (its implied projection on the z axis) by further manipulating the attributes of the geometric objects, for instance their size:

ggplot(data = energy_and_emissions, aes(x = GDPPC, y = EPC, color = CO2PC_1995, size = CO2PC_1995)) + 
  geom_point() + 
  scale_color_viridis_c(direction = -1)

With some imagination, you can maybe visualize in your mind the scatter of points on the flattened third-dimension (the z-axis).

3D plots with plot3D

GDPPC <- energy_and_emissions$GDPPC
EPC <- energy_and_emissions$EPC
CO2PC_1995 <- energy_and_emissions$CO2PC_1995
scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995)

Color palettes

There are preset palettes (the default is jet.col(n) with n, the number of colors to generate, set to 100). Alternatives include: jet2.col(n). This palette is similar to jet.col but brighter, since it lacks the deeper blues and reds:

scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995,
          col = jet2.col(n = 100))

Palette gg.col(n) uses colors similar to those used in ggplot2:

scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995,
          col = gg.col(n = 50))

The function ramp.col() creates a sequence of colors by interpolation, and is based on two or three colors, for example:

scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995,
          col = ramp.col(col = c("green", "red"), n = 50, alpha = 1))

And with three colors:

scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995,
          col = ramp.col(col = c("blue", "yellow", "red"), n = 50, alpha = 1))

Aspect of plot and annotations

The shape of the markers can be changed by means of the argument pch. Here is a list of symbols available for plotting, The size of the symbol is controled by the argument cex:

scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995, 
          pch = 0,
          cex = 1.5)

We can also add annotations, such as labels and a legend:

scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995, 
          pch = 20,
          cex = 1.5,
          xlab = "GDPPC", ylab = "EPC", zlab = "CO2",
          clab = c("CO2 Emissions", "per Capita", "(kilotonnes)"))
scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995, 
          pch = 20,
          cex = 1.5,
          labels = row.names(energy_and_emissions),
          xlab = "GDPPC", ylab = "EPC", zlab = "CO2",
          clab = c("CO2 Emissions", "per Capita", "(kilotonnes)"))

text3D(x = GDPPC, y = EPC, z = CO2PC_1995, 
       cex = 0.75,
       labels = energy_and_emissions$Continent,
       add = TRUE)

Changing the perspective

Two arguments control the viewing direction: theta and phi. These two are angles, theta is the angle with respect to the azimuth, whereas phi is the colatitude. The default values are \(\theta = 40\) and \(\phi = 40\) (in degrees).

We can see the effect of changing these values. For instance, if we change the value of theta, the perspective rotates with respect to the z-axis:

scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995, 
          pch = 20,
          cex = 1.5,
          theta = 0,
          labels = row.names(energy_and_emissions),
          xlab = "GDPPC", ylab = "EPC", zlab = "CO2",
          clab = c("CO2 Emissions", "per Capita", "(kilotonnes)"))

If we change the value of phi, the perspective rotates with respect to the x-y plane:

scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995, 
          pch = 20,
          cex = 1.5,
          theta = 40,
          phi = 0,
          labels = row.names(energy_and_emissions),
          xlab = "GDPPC", ylab = "EPC", zlab = "CO2",
          clab = c("CO2 Emissions", "per Capita", "(kilotonnes)"))

Interactive 3D plots with plot3Drgl

Package plot3Drgl can be used to create interactive plots based on objects created with plot3D. The function plotrgl() will create an interactive version of the most recent plot3D object:

scatter_labels <- scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995, 
          pch = 20,
          cex = 1.5,
          labels = row.names(energy_and_emissions),
          xlab = "GDPPC", ylab = "EPC", zlab = "CO2",
          clab = c("CO2 Emissions", "per Capita", "(kilotonnes)"))

text3D(x = GDPPC, y = EPC, z = CO2PC_1995, 
       cex = 0.75,
       labels = energy_and_emissions$Continent,
       add = TRUE)

plotrgl()

Other 3D objects

There are other types of objects (besides points and text) that can be plotted in 3D using plot3D, including:

  • 3D lines
  • 3D ribbons
  • 3D slices
  • 3D surfaces

Compute the linear regression (z = ax + by + d)

mod <- lm(CO2PC_1995 ~ GDPPC + EPC)
summary(mod)

Call:
lm(formula = CO2PC_1995 ~ GDPPC + EPC)

Residuals:
       Min         1Q     Median         3Q        Max 
-0.0074792 -0.0012215 -0.0009659  0.0008195  0.0097414 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) 0.0011333  0.0002589   4.378 2.00e-05 ***
GDPPC       0.0105080  0.0013890   7.565 1.76e-12 ***
EPC         0.0374944  0.0088802   4.222 3.79e-05 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.002753 on 185 degrees of freedom
Multiple R-squared:  0.5043,    Adjusted R-squared:  0.4989 
F-statistic:  94.1 on 2 and 185 DF,  p-value: < 2.2e-16

Predict values on regular xy grid

grid.lines = 40
GDPPC.pred <- seq(min(GDPPC), max(GDPPC), length.out = grid.lines)
EPC.pred <- seq(min(EPC), max(EPC), length.out = grid.lines)
xy <- expand.grid(GDPPC = GDPPC.pred, EPC = EPC.pred)
CO2.pred <- matrix(predict(mod, newdata = xy), 
                 nrow = grid.lines, ncol = grid.lines)

Fitted points for droplines to surface

fitpoints <- predict(mod)

Scatter plot with regression plane

scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995, 
          pch = 20,
          cex = 1.5,
          theta = 30,
          phi = 20,
          labels = row.names(energy_and_emissions),
          xlab = "GDPPC", ylab = "EPC", zlab = "CO2",
          clab = c("CO2 Emissions", "per Capita", "(kilotonnes)"),
          surf = list(x = GDPPC.pred, 
                y = EPC.pred, 
                z = CO2.pred,  
                facets = NA, 
                fit = fitpoints))

scatter3D(x = GDPPC, y = EPC, z = CO2PC_1995, 
          pch = 20,
          cex = 1.5,
          theta = 30,
          phi = 20,
          labels = row.names(energy_and_emissions),
          xlab = "GDPPC", ylab = "EPC", zlab = "CO2",
          clab = c("CO2 Emissions", "per Capita", "(kilotonnes)"),
          surf = list(x = GDPPC.pred, 
                y = EPC.pred, 
                z = CO2.pred,  
                facets = NA, 
                fit = fitpoints))

text3D(x = GDPPC, y = EPC, z = CO2PC_1995, 
       cex = 0.75,
       labels = energy_and_emissions$Country,
       add = TRUE)


plotrgl()

Interactive 3D plots with plotly

colors <- c('#4AC6B7', '#1972A4', '#965F8A', '#FF7070', '#C61951')

plot_ly(energy_and_emissions, x = ~GDPPC, y = ~EPC, z = ~CO2PC_1995, color = ~Continent, size = ~CO2PC_1995, colors = colors,
             marker = list(symbol = 'circle', sizemode = 'diameter',
                      line = list(width = 2, color = '#000000')), 
                      sizes = c(5, 40),
             text = ~paste('Country:', Country, 
                           '<br>GDP per capita:', round(GDPPC, 3), 
                           '<br>Energy Consumption:', round(EPC,3),
                           '<br>CO2 emmissions 1995:',  round(CO2PC_1995, 3))) %>%
  layout(title = 'GDP per capita v. Energy consumption per capita',
         scene = list(xaxis = list(title = 'GDP per capita',
                      gridcolor = 'rgb(255, 255, 255)',
                      zerolinewidth = 1,
                      ticklen = 5,
                      gridwidth = 2),
               yaxis = list(title = 'Energy Consumption',
                      gridcolor = 'rgb(255, 255, 255)',
                      zerolinewidth = 1,
                      ticklen = 5,
                      gridwith = 2),
               zaxis = list(title = 'CO2 Emissions',
                            gridcolor = 'rgb(255, 255, 255)',
                            zerolinewidth = 1,
                            ticklen = 5,
                            gridwith = 2)),
         paper_bgcolor = 'rgb(243, 243, 243)',
         plot_bgcolor = 'rgb(243, 243, 243)')
co2_95to15 <- energy_and_emissions %>% 
  dplyr::select(Country, Continent, GDPPC, EPC, CO2PC_1995, CO2PC_2005, CO2PC_2015) %>% # First select relevant variables
  gather(Year, CO2, -c(Country, Continent, GDPPC, EPC)) %>% # Gather CO2 columns: it is important to exclude from this operation the columns Country and GDP
  mutate(Year = factor(Year, 
                       levels = c("CO2PC_1995", "CO2PC_2005", "CO2PC_2015"),
                       labels = c("1995", "2005", "2015"))) # Relabel the years
plot_ly(co2_95to15, x = ~GDPPC, y = ~EPC, z = ~CO2, 
        color = ~Continent, 
        size = ~CO2, 
        colors = colors,
        frame = ~Year,
        marker = list(symbol = 'circle', sizemode = 'diameter',
                      line = list(width = 2, color = '#000000')), 
                      sizes = c(5, 40),
        text = ~paste('Country:', Country, 
                      '<br>GDP per capita:', round(GDPPC, 3), 
                      '<br>Energy Consumption:', round(EPC,3),
                      '<br>CO2 emmissions 1995:',  round(CO2PC_1995, 3))) %>%
  layout(title = 'GDP per capita v. Energy consumption per capita',
         scene = list(xaxis = list(title = 'GDP per capita',
                      gridcolor = 'rgb(255, 255, 255)',
                      zerolinewidth = 1,
                      ticklen = 5,
                      gridwidth = 2),
               yaxis = list(title = 'Energy Consumption',
                      gridcolor = 'rgb(255, 255, 255)',
                      zerolinewidth = 1,
                      ticklen = 5,
                      gridwith = 2),
               zaxis = list(title = 'CO2 Emissions',
                            gridcolor = 'rgb(255, 255, 255)',
                            range = c(0, 0.1),
                            zerolinewidth = 1,
                            ticklen = 5,
                            gridwith = 2)),
         paper_bgcolor = 'rgb(243, 243, 243)',
         plot_bgcolor = 'rgb(243, 243, 243)') %>% 
  animation_opts(
    1300, redraw = FALSE
  )
LS0tDQp0aXRsZTogIjNEIFBsb3R0aW5nIGluIFI6IEV4YW1wbGVzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KUGxvdHRpbmcgaW4gM0QgaGFzIG1hbnkgdXNlZnVsIGFwcGxpY2F0aW9ucy4gVGhpcyBub3RlYm9vayB3aWxsIGlsbHVzdHJhdGUgc29tZSB0ZWNobmlxdWVzIHVzZWQgdG8gY3JlYXRlIDNEIHBsb3RzIGluIGBSYC4gVGhlcmUgYXJlIG51bWVyb3VzIHJlc291cmNlcyB0aGF0IGNhbiBiZSB1c2VkIGZvciB0aGlzIHB1cnBvc2UuIA0KDQojIyBQcmVsaW1pbmFyaWVzDQoNCkxvYWQgdGhlIHBhY2thZ2VzIHVzZWQgaW4gdGhpcyBub3RlYm9vay4gYHBhY2tyYCBjb250YWlucyBhIHNhbXBsZSBkYXRhIHNldCwgYHRpZHl2ZXJzZWAgaXMgYSBjb2xsZWN0aW9uIG9mIHBhY2thZ2VzIGZvciBkYXRhIG1hbmlwdWxhdGlvbiBhbmQgdmlzdWFsaXphdGlvbiwgYHBsb3QzRGAgaXMgYSBwYWNrYWdlIGZvciBjcmVhdGluZyAzRCBwbG90cywgYHBsb3QzRHJnbGAgaXMgYSBwYWNrYWdlIHRvIGNvbnZlcnQgYHBsb3QzRGAgc3RhdGljIHBsb3RzIGludG8gaW50ZXJhY3RpdmUgcGxvdHMsIGFuZCBgcGxvdGx5YCBpcyBhIHBhY2thZ2UgZm9yIGNyZWF0aW5nIGludGVyYWN0aXZlIHBsb3RzOg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkocGFja3IpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkocGxvdDNEKQ0KbGlicmFyeShwbG90M0RyZ2wpDQpsaWJyYXJ5KHBsb3RseSkNCmBgYA0KDQpTdW1tb24gdGhlIGRhdGEgc2V0Og0KYGBge3J9DQpkYXRhKGVuZXJneV9hbmRfZW1pc3Npb25zKQ0KYGBgDQoNClF1aWNrbHkgcmV2aWV3IHRoZSBjb250ZW50cyBvZiB0aGUgZGF0YSBzZXQ6DQpgYGB7cn0NCnN1bW1hcnkoZW5lcmd5X2FuZF9lbWlzc2lvbnMpDQpgYGANCg0KU29tZSB2YXJpYWJsZXMgYXJlIGFic29sdXRlIHZhbHVlcyAoZS5nLiwgcG9wdWxhdGlvbikgYW5kIG90aGVycyBhcmUgcmF0ZXMgKGUuZy4sIEdEUCBwZXIgY2FwaXRhKS4gTXV0YXRlIHRoZSBkYXRhZnJhbWUgdG8gb2JhaW4gdmFyaWFibGVzIGZvciBlbmVyZ3kgY29uc3VtcHRpb24gYW5kIGVtaXNzaW9ucyB0aGF0IGFyZSByYXRlczoNCmBgYHtyfQ0KZW5lcmd5X2FuZF9lbWlzc2lvbnMgPC0gZW5lcmd5X2FuZF9lbWlzc2lvbnMgJT4lDQogIG11dGF0ZShHRFBQQyA9IEdEUFBDIC8gMTAwMDAwLA0KICAgICAgICAgRVBDID0gYmJscGQvUG9wdWxhdGlvbiwgDQogICAgICAgICBDTzJQQ18xOTk1ID0gQ08yXzE5OTUvUG9wdWxhdGlvbiwgDQogICAgICAgICBDTzJQQ18yMDA1ID0gQ08yXzIwMDUvUG9wdWxhdGlvbiwgDQogICAgICAgICBDTzJQQ18yMDE1ID0gQ08yXzIwMTUvUG9wdWxhdGlvbikNCmBgYA0KDQojIyBQc2V1ZG8tM0QgcGxvdHMNCg0KVGhlIGZpcnN0IHRoaW5nIHRvIG5vdGUgaXMgdGhhdCBhbnkgcGxvdHMgcmVuZGVyZWQgb24gYSBmbGF0IHN1cmZhY2Ugc3VjaCBhcyBhIHBhZ2UgaW4gYSBib29rIG9yIGEgY29tcHV0ZXIgc2NyZWVuIGFyZSBwc2V1ZG8tM0QgaW4gdGhlIHNlbnNlIHRoYXQgdGhleSBvbmx5IGV4aXN0IGluIDJELiBUaGF0IHNhaWQsIHRoZXJlIGFyZSBkaWZmZXJlbnQgd2F5cyBvZiByZXByZXNlbnRpbmcgMyBhbmQgZXZlbiBoaWdoZXIgZGltZW5zaW9ucyBpbiBhIDJEIHN1cmZhY2UuDQoNCldlIHdpbGwgYmVnaW4gd2l0aCBzb21lIHBsb3RzIHRoYXQgYXJlIG1vcmUgcHNldWRvLTNEIHRoYW4gb3RoZXJzLg0KDQpQZXJoYXBzIHRoZSBzaW1wbGVzdCB3YXkgdG8gcmVwcmVzZW50IGEgM0QgcGxvdCBpbiB0d28gZGltZW5zaW9ucyBpcyBieSBfcHJvamVjdGluZ18gb25lIG9mIHRoZSBkaW1lbnNpb25zIG9uIHRoZSBvdGhlciB0d28sIGVmZmVjdGl2ZWx5IGZsYXR0ZW5pbmcgaXQgYnV0IGluIHN1Y2ggYSB3YXkgdGhhdCBhbiBhc3BlY3Qgb2YgdGhlIDNEIGlzIHN0aWxsIGxlZ2libGUgZnJvbSB0aGUgcGxvdC4NCg0KQ29uc2lkZXIgdGhlIGBlbmVyZ3lfYW5kX2VtaXNzaW9uc2AgZGF0YSBzZXQuIEl0IGNvbnRhaW5zIHNldmVyYWwgdmFyaWFibGVzLCBpbmNsdWRpbmcgcG9wdWxhdGlvbiBHRFBQQywgb2lsIGNvbnN1bXB0aW9uIGluIGJhcnJlbHMgb2Ygb2lsIHBlciBkYXksIGFuZCAkQ09fMiQgZW1pc3Npb25zLg0KDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBlbmVyZ3lfYW5kX2VtaXNzaW9ucywgYWVzKHggPSBHRFBQQywgeSA9IENPMlBDXzE5OTUpKSArIGdlb21fcG9pbnQoKQ0KYGBgDQoNCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGVuZXJneV9hbmRfZW1pc3Npb25zLCBhZXMoeCA9IEVQQywgeSA9IENPMlBDXzE5OTUpKSArIGdlb21fcG9pbnQoKQ0KYGBgDQoNCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGVuZXJneV9hbmRfZW1pc3Npb25zLCBhZXMoeCA9IEdEUFBDLCB5ID0gRVBDLCBjb2xvciA9IENPMlBDXzE5OTUpKSArIA0KICBnZW9tX3BvaW50KCkgKyANCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKGRpcmVjdGlvbiA9IC0xKQ0KYGBgDQoNCkluIHRoaXMgcGxvdCwgd2UgImZsYXR0ZW4iIHRoZSB2YXJpYWJsZSBmb3IgZW1pc3Npb25zLCBwcm9qZWN0aW5nIGl0IG9uIHRoZSAyRCBwbGFuZSBvZiBHRFAgcGVyIGNhcGl0YSBhbmQgZW5lcmd5IGNvbnN1bXB0aW9ucyBwZXIgY2FwaXRhLCBhbmQgcmVwcmVzZW50IHRoZSAiaGVpZ2h0IiBieSBtZWFucyBvZiBjb2xvcnMuIFdlIGNhbiBmdXJ0aGVyIGVtcGhhc2l6ZSB0aGUgImhlaWdodCIgb2YgdGhlIGVtaXNzaW9ucyB2YXJpYWJsZSAoaXRzIGltcGxpZWQgcHJvamVjdGlvbiBvbiB0aGUgeiBheGlzKSBieSBmdXJ0aGVyIG1hbmlwdWxhdGluZyB0aGUgYXR0cmlidXRlcyBvZiB0aGUgZ2VvbWV0cmljIG9iamVjdHMsIGZvciBpbnN0YW5jZSB0aGVpciBzaXplOg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGVuZXJneV9hbmRfZW1pc3Npb25zLCBhZXMoeCA9IEdEUFBDLCB5ID0gRVBDLCBjb2xvciA9IENPMlBDXzE5OTUsIHNpemUgPSBDTzJQQ18xOTk1KSkgKyANCiAgZ2VvbV9wb2ludCgpICsgDQogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYyhkaXJlY3Rpb24gPSAtMSkNCmBgYA0KDQpXaXRoIHNvbWUgaW1hZ2luYXRpb24sIHlvdSBjYW4gbWF5YmUgdmlzdWFsaXplIGluIHlvdXIgbWluZCB0aGUgc2NhdHRlciBvZiBwb2ludHMgb24gdGhlIGZsYXR0ZW5lZCB0aGlyZC1kaW1lbnNpb24gKHRoZSB6LWF4aXMpLg0KDQojIyAzRCBwbG90cyB3aXRoIGBwbG90M0RgDQoNCmBgYHtyfQ0KR0RQUEMgPC0gZW5lcmd5X2FuZF9lbWlzc2lvbnMkR0RQUEMNCkVQQyA8LSBlbmVyZ3lfYW5kX2VtaXNzaW9ucyRFUEMNCkNPMlBDXzE5OTUgPC0gZW5lcmd5X2FuZF9lbWlzc2lvbnMkQ08yUENfMTk5NQ0KYGBgDQoNCg0KYGBge3J9DQpzY2F0dGVyM0QoeCA9IEdEUFBDLCB5ID0gRVBDLCB6ID0gQ08yUENfMTk5NSkNCmBgYA0KDQojIyMgQ29sb3IgcGFsZXR0ZXMNCg0KVGhlcmUgYXJlIHByZXNldCBwYWxldHRlcyAodGhlIGRlZmF1bHQgaXMgYGpldC5jb2wobilgIHdpdGggYG5gLCB0aGUgbnVtYmVyIG9mIGNvbG9ycyB0byBnZW5lcmF0ZSwgc2V0IHRvIDEwMCkuIEFsdGVybmF0aXZlcyBpbmNsdWRlOiBgamV0Mi5jb2wobilgLiBUaGlzIHBhbGV0dGUgaXMgc2ltaWxhciB0byBgamV0LmNvbGAgYnV0IGJyaWdodGVyLCBzaW5jZSBpdCBsYWNrcyB0aGUgZGVlcGVyIGJsdWVzIGFuZCByZWRzOg0KYGBge3J9DQpzY2F0dGVyM0QoeCA9IEdEUFBDLCB5ID0gRVBDLCB6ID0gQ08yUENfMTk5NSwNCiAgICAgICAgICBjb2wgPSBqZXQyLmNvbChuID0gMTAwKSkNCmBgYA0KDQpQYWxldHRlIGBnZy5jb2wobilgIHVzZXMgY29sb3JzIHNpbWlsYXIgdG8gdGhvc2UgdXNlZCBpbiBgZ2dwbG90MmA6DQpgYGB7cn0NCnNjYXR0ZXIzRCh4ID0gR0RQUEMsIHkgPSBFUEMsIHogPSBDTzJQQ18xOTk1LA0KICAgICAgICAgIGNvbCA9IGdnLmNvbChuID0gNTApKQ0KYGBgDQoNClRoZSBmdW5jdGlvbiBgcmFtcC5jb2woKWAgY3JlYXRlcyBhIHNlcXVlbmNlIG9mIGNvbG9ycyBieSBpbnRlcnBvbGF0aW9uLCBhbmQgaXMgYmFzZWQgb24gdHdvIG9yIHRocmVlIGNvbG9ycywgZm9yIGV4YW1wbGU6DQpgYGB7cn0NCnNjYXR0ZXIzRCh4ID0gR0RQUEMsIHkgPSBFUEMsIHogPSBDTzJQQ18xOTk1LA0KICAgICAgICAgIGNvbCA9IHJhbXAuY29sKGNvbCA9IGMoImdyZWVuIiwgInJlZCIpLCBuID0gNTAsIGFscGhhID0gMSkpDQpgYGANCg0KQW5kIHdpdGggdGhyZWUgY29sb3JzOg0KYGBge3J9DQpzY2F0dGVyM0QoeCA9IEdEUFBDLCB5ID0gRVBDLCB6ID0gQ08yUENfMTk5NSwNCiAgICAgICAgICBjb2wgPSByYW1wLmNvbChjb2wgPSBjKCJibHVlIiwgInllbGxvdyIsICJyZWQiKSwgbiA9IDUwLCBhbHBoYSA9IDEpKQ0KYGBgDQoNCiMjIyBBc3BlY3Qgb2YgcGxvdCBhbmQgYW5ub3RhdGlvbnMNCg0KVGhlIHNoYXBlIG9mIHRoZSBtYXJrZXJzIGNhbiBiZSBjaGFuZ2VkIGJ5IG1lYW5zIG9mIHRoZSBhcmd1bWVudCBgcGNoYC4gSGVyZSBpcyBhIFtsaXN0IG9mIHN5bWJvbHNdKGh0dHA6Ly93d3cuc3RoZGEuY29tL2VuZ2xpc2gvd2lraS9yLXBsb3QtcGNoLXN5bWJvbHMtdGhlLWRpZmZlcmVudC1wb2ludC1zaGFwZXMtYXZhaWxhYmxlLWluLXIpIGF2YWlsYWJsZSBmb3IgcGxvdHRpbmcsIFRoZSBzaXplIG9mIHRoZSBzeW1ib2wgaXMgY29udHJvbGVkIGJ5IHRoZSBhcmd1bWVudCBgY2V4YDoNCmBgYHtyfQ0Kc2NhdHRlcjNEKHggPSBHRFBQQywgeSA9IEVQQywgeiA9IENPMlBDXzE5OTUsIA0KICAgICAgICAgIHBjaCA9IDAsDQogICAgICAgICAgY2V4ID0gMS41KQ0KYGBgDQoNCldlIGNhbiBhbHNvIGFkZCBhbm5vdGF0aW9ucywgc3VjaCBhcyBsYWJlbHMgYW5kIGEgbGVnZW5kOg0KYGBge3J9DQpzY2F0dGVyM0QoeCA9IEdEUFBDLCB5ID0gRVBDLCB6ID0gQ08yUENfMTk5NSwgDQogICAgICAgICAgcGNoID0gMjAsDQogICAgICAgICAgY2V4ID0gMS41LA0KICAgICAgICAgIHhsYWIgPSAiR0RQUEMiLCB5bGFiID0gIkVQQyIsIHpsYWIgPSAiQ08yIiwNCiAgICAgICAgICBjbGFiID0gYygiQ08yIEVtaXNzaW9ucyIsICJwZXIgQ2FwaXRhIiwgIihraWxvdG9ubmVzKSIpKQ0KYGBgDQoNCmBgYHtyfQ0Kc2NhdHRlcjNEKHggPSBHRFBQQywgeSA9IEVQQywgeiA9IENPMlBDXzE5OTUsIA0KICAgICAgICAgIHBjaCA9IDIwLA0KICAgICAgICAgIGNleCA9IDEuNSwNCiAgICAgICAgICBsYWJlbHMgPSByb3cubmFtZXMoZW5lcmd5X2FuZF9lbWlzc2lvbnMpLA0KICAgICAgICAgIHhsYWIgPSAiR0RQUEMiLCB5bGFiID0gIkVQQyIsIHpsYWIgPSAiQ08yIiwNCiAgICAgICAgICBjbGFiID0gYygiQ08yIEVtaXNzaW9ucyIsICJwZXIgQ2FwaXRhIiwgIihraWxvdG9ubmVzKSIpKQ0KDQp0ZXh0M0QoeCA9IEdEUFBDLCB5ID0gRVBDLCB6ID0gQ08yUENfMTk5NSwgDQogICAgICAgY2V4ID0gMC43NSwNCiAgICAgICBsYWJlbHMgPSBlbmVyZ3lfYW5kX2VtaXNzaW9ucyRDb250aW5lbnQsDQogICAgICAgYWRkID0gVFJVRSkNCmBgYA0KDQojIyMgQ2hhbmdpbmcgdGhlIHBlcnNwZWN0aXZlDQoNClR3byBhcmd1bWVudHMgY29udHJvbCB0aGUgdmlld2luZyBkaXJlY3Rpb246IGB0aGV0YWAgYW5kIGBwaGlgLiBUaGVzZSB0d28gYXJlIGFuZ2xlcywgYHRoZXRhYCBpcyB0aGUgYW5nbGUgd2l0aCByZXNwZWN0IHRvIHRoZSBhemltdXRoLCB3aGVyZWFzIGBwaGlgIGlzIHRoZSBjb2xhdGl0dWRlLiBUaGUgZGVmYXVsdCB2YWx1ZXMgYXJlICRcdGhldGEgPSA0MCQgYW5kICRccGhpID0gNDAkIChpbiBkZWdyZWVzKS4NCg0KV2UgY2FuIHNlZSB0aGUgZWZmZWN0IG9mIGNoYW5naW5nIHRoZXNlIHZhbHVlcy4gRm9yIGluc3RhbmNlLCBpZiB3ZSBjaGFuZ2UgdGhlIHZhbHVlIG9mIGB0aGV0YWAsIHRoZSBwZXJzcGVjdGl2ZSByb3RhdGVzIHdpdGggcmVzcGVjdCB0byB0aGUgei1heGlzOg0KYGBge3J9DQpzY2F0dGVyM0QoeCA9IEdEUFBDLCB5ID0gRVBDLCB6ID0gQ08yUENfMTk5NSwgDQogICAgICAgICAgcGNoID0gMjAsDQogICAgICAgICAgY2V4ID0gMS41LA0KICAgICAgICAgIHRoZXRhID0gMCwNCiAgICAgICAgICBsYWJlbHMgPSByb3cubmFtZXMoZW5lcmd5X2FuZF9lbWlzc2lvbnMpLA0KICAgICAgICAgIHhsYWIgPSAiR0RQUEMiLCB5bGFiID0gIkVQQyIsIHpsYWIgPSAiQ08yIiwNCiAgICAgICAgICBjbGFiID0gYygiQ08yIEVtaXNzaW9ucyIsICJwZXIgQ2FwaXRhIiwgIihraWxvdG9ubmVzKSIpKQ0KYGBgDQoNCklmIHdlIGNoYW5nZSB0aGUgdmFsdWUgb2YgYHBoaWAsIHRoZSBwZXJzcGVjdGl2ZSByb3RhdGVzIHdpdGggcmVzcGVjdCB0byB0aGUgeC15IHBsYW5lOg0KYGBge3J9DQpzY2F0dGVyM0QoeCA9IEdEUFBDLCB5ID0gRVBDLCB6ID0gQ08yUENfMTk5NSwgDQogICAgICAgICAgcGNoID0gMjAsDQogICAgICAgICAgY2V4ID0gMS41LA0KICAgICAgICAgIHRoZXRhID0gNDAsDQogICAgICAgICAgcGhpID0gMCwNCiAgICAgICAgICBsYWJlbHMgPSByb3cubmFtZXMoZW5lcmd5X2FuZF9lbWlzc2lvbnMpLA0KICAgICAgICAgIHhsYWIgPSAiR0RQUEMiLCB5bGFiID0gIkVQQyIsIHpsYWIgPSAiQ08yIiwNCiAgICAgICAgICBjbGFiID0gYygiQ08yIEVtaXNzaW9ucyIsICJwZXIgQ2FwaXRhIiwgIihraWxvdG9ubmVzKSIpKQ0KYGBgDQoNCiMjIyBJbnRlcmFjdGl2ZSAzRCBwbG90cyB3aXRoIGBwbG90M0RyZ2xgDQoNClBhY2thZ2UgYHBsb3QzRHJnbGAgY2FuIGJlIHVzZWQgdG8gY3JlYXRlIGludGVyYWN0aXZlIHBsb3RzIGJhc2VkIG9uIG9iamVjdHMgY3JlYXRlZCB3aXRoIGBwbG90M0RgLiBUaGUgZnVuY3Rpb24gYHBsb3RyZ2woKWAgd2lsbCBjcmVhdGUgYW4gaW50ZXJhY3RpdmUgdmVyc2lvbiBvZiB0aGUgbW9zdCByZWNlbnQgYHBsb3QzRGAgb2JqZWN0Og0KYGBge3IgZXZhbD1GQUxTRX0NCnNjYXR0ZXJfbGFiZWxzIDwtIHNjYXR0ZXIzRCh4ID0gR0RQUEMsIHkgPSBFUEMsIHogPSBDTzJQQ18xOTk1LCANCiAgICAgICAgICBwY2ggPSAyMCwNCiAgICAgICAgICBjZXggPSAxLjUsDQogICAgICAgICAgbGFiZWxzID0gcm93Lm5hbWVzKGVuZXJneV9hbmRfZW1pc3Npb25zKSwNCiAgICAgICAgICB4bGFiID0gIkdEUFBDIiwgeWxhYiA9ICJFUEMiLCB6bGFiID0gIkNPMiIsDQogICAgICAgICAgY2xhYiA9IGMoIkNPMiBFbWlzc2lvbnMiLCAicGVyIENhcGl0YSIsICIoa2lsb3Rvbm5lcykiKSkNCg0KdGV4dDNEKHggPSBHRFBQQywgeSA9IEVQQywgeiA9IENPMlBDXzE5OTUsIA0KICAgICAgIGNleCA9IDAuNzUsDQogICAgICAgbGFiZWxzID0gZW5lcmd5X2FuZF9lbWlzc2lvbnMkQ29udGluZW50LA0KICAgICAgIGFkZCA9IFRSVUUpDQoNCnBsb3RyZ2woKQ0KYGBgDQoNCiMjIyBPdGhlciAzRCBvYmplY3RzDQoNClRoZXJlIGFyZSBvdGhlciB0eXBlcyBvZiBvYmplY3RzIChiZXNpZGVzIHBvaW50cyBhbmQgdGV4dCkgdGhhdCBjYW4gYmUgcGxvdHRlZCBpbiAzRCB1c2luZyBgcGxvdDNEYCwgaW5jbHVkaW5nOg0KDQotIDNEIGxpbmVzDQotIDNEIHJpYmJvbnMNCi0gM0Qgc2xpY2VzDQotIDNEIHN1cmZhY2VzDQoNCkNvbXB1dGUgdGhlIGxpbmVhciByZWdyZXNzaW9uICh6ID0gYXggKyBieSArIGQpDQpgYGB7cn0NCm1vZCA8LSBsbShDTzJQQ18xOTk1IH4gR0RQUEMgKyBFUEMpDQpzdW1tYXJ5KG1vZCkNCmBgYA0KDQpQcmVkaWN0IHZhbHVlcyBvbiByZWd1bGFyIHh5IGdyaWQNCmBgYHtyfQ0KZ3JpZC5saW5lcyA9IDQwDQpHRFBQQy5wcmVkIDwtIHNlcShtaW4oR0RQUEMpLCBtYXgoR0RQUEMpLCBsZW5ndGgub3V0ID0gZ3JpZC5saW5lcykNCkVQQy5wcmVkIDwtIHNlcShtaW4oRVBDKSwgbWF4KEVQQyksIGxlbmd0aC5vdXQgPSBncmlkLmxpbmVzKQ0KeHkgPC0gZXhwYW5kLmdyaWQoR0RQUEMgPSBHRFBQQy5wcmVkLCBFUEMgPSBFUEMucHJlZCkNCkNPMi5wcmVkIDwtIG1hdHJpeChwcmVkaWN0KG1vZCwgbmV3ZGF0YSA9IHh5KSwgDQogICAgICAgICAgICAgICAgIG5yb3cgPSBncmlkLmxpbmVzLCBuY29sID0gZ3JpZC5saW5lcykNCmBgYA0KDQpGaXR0ZWQgcG9pbnRzIGZvciBkcm9wbGluZXMgdG8gc3VyZmFjZQ0KYGBge3J9DQpmaXRwb2ludHMgPC0gcHJlZGljdChtb2QpDQpgYGANCg0KU2NhdHRlciBwbG90IHdpdGggcmVncmVzc2lvbiBwbGFuZQ0KYGBge3J9DQpzY2F0dGVyM0QoeCA9IEdEUFBDLCB5ID0gRVBDLCB6ID0gQ08yUENfMTk5NSwgDQogICAgICAgICAgcGNoID0gMjAsDQogICAgICAgICAgY2V4ID0gMS41LA0KICAgICAgICAgIHRoZXRhID0gMzAsDQogICAgICAgICAgcGhpID0gMjAsDQogICAgICAgICAgbGFiZWxzID0gcm93Lm5hbWVzKGVuZXJneV9hbmRfZW1pc3Npb25zKSwNCiAgICAgICAgICB4bGFiID0gIkdEUFBDIiwgeWxhYiA9ICJFUEMiLCB6bGFiID0gIkNPMiIsDQogICAgICAgICAgY2xhYiA9IGMoIkNPMiBFbWlzc2lvbnMiLCAicGVyIENhcGl0YSIsICIoa2lsb3Rvbm5lcykiKSwNCiAgICAgICAgICBzdXJmID0gbGlzdCh4ID0gR0RQUEMucHJlZCwgDQogICAgICAgICAgICAgICAgeSA9IEVQQy5wcmVkLCANCiAgICAgICAgICAgICAgICB6ID0gQ08yLnByZWQsICANCiAgICAgICAgICAgICAgICBmYWNldHMgPSBOQSwgDQogICAgICAgICAgICAgICAgZml0ID0gZml0cG9pbnRzKSkNCmBgYA0KDQpgYGB7cn0NCnNjYXR0ZXIzRCh4ID0gR0RQUEMsIHkgPSBFUEMsIHogPSBDTzJQQ18xOTk1LCANCiAgICAgICAgICBwY2ggPSAyMCwNCiAgICAgICAgICBjZXggPSAxLjUsDQogICAgICAgICAgdGhldGEgPSAzMCwNCiAgICAgICAgICBwaGkgPSAyMCwNCiAgICAgICAgICBsYWJlbHMgPSByb3cubmFtZXMoZW5lcmd5X2FuZF9lbWlzc2lvbnMpLA0KICAgICAgICAgIHhsYWIgPSAiR0RQUEMiLCB5bGFiID0gIkVQQyIsIHpsYWIgPSAiQ08yIiwNCiAgICAgICAgICBjbGFiID0gYygiQ08yIEVtaXNzaW9ucyIsICJwZXIgQ2FwaXRhIiwgIihraWxvdG9ubmVzKSIpLA0KICAgICAgICAgIHN1cmYgPSBsaXN0KHggPSBHRFBQQy5wcmVkLCANCiAgICAgICAgICAgICAgICB5ID0gRVBDLnByZWQsIA0KICAgICAgICAgICAgICAgIHogPSBDTzIucHJlZCwgIA0KICAgICAgICAgICAgICAgIGZhY2V0cyA9IE5BLCANCiAgICAgICAgICAgICAgICBmaXQgPSBmaXRwb2ludHMpKQ0KDQp0ZXh0M0QoeCA9IEdEUFBDLCB5ID0gRVBDLCB6ID0gQ08yUENfMTk5NSwgDQogICAgICAgY2V4ID0gMC43NSwNCiAgICAgICBsYWJlbHMgPSBlbmVyZ3lfYW5kX2VtaXNzaW9ucyRDb3VudHJ5LA0KICAgICAgIGFkZCA9IFRSVUUpDQoNCnBsb3RyZ2woKQ0KYGBgDQoNCiMjIEludGVyYWN0aXZlIDNEIHBsb3RzIHdpdGggYHBsb3RseWANCg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KY29sb3JzIDwtIGMoJyM0QUM2QjcnLCAnIzE5NzJBNCcsICcjOTY1RjhBJywgJyNGRjcwNzAnLCAnI0M2MTk1MScpDQoNCnBsb3RfbHkoZW5lcmd5X2FuZF9lbWlzc2lvbnMsIHggPSB+R0RQUEMsIHkgPSB+RVBDLCB6ID0gfkNPMlBDXzE5OTUsIGNvbG9yID0gfkNvbnRpbmVudCwgc2l6ZSA9IH5DTzJQQ18xOTk1LCBjb2xvcnMgPSBjb2xvcnMsDQogICAgICAgICAgICAgbWFya2VyID0gbGlzdChzeW1ib2wgPSAnY2lyY2xlJywgc2l6ZW1vZGUgPSAnZGlhbWV0ZXInLA0KICAgICAgICAgICAgICAgICAgICAgIGxpbmUgPSBsaXN0KHdpZHRoID0gMiwgY29sb3IgPSAnIzAwMDAwMCcpKSwgDQogICAgICAgICAgICAgICAgICAgICAgc2l6ZXMgPSBjKDUsIDQwKSwNCiAgICAgICAgICAgICB0ZXh0ID0gfnBhc3RlKCdDb3VudHJ5OicsIENvdW50cnksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxicj5HRFAgcGVyIGNhcGl0YTonLCByb3VuZChHRFBQQywgMyksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxicj5FbmVyZ3kgQ29uc3VtcHRpb246Jywgcm91bmQoRVBDLDMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxicj5DTzIgZW1taXNzaW9ucyAxOTk1OicsICByb3VuZChDTzJQQ18xOTk1LCAzKSkpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAnR0RQIHBlciBjYXBpdGEgdi4gRW5lcmd5IGNvbnN1bXB0aW9uIHBlciBjYXBpdGEnLA0KICAgICAgICAgc2NlbmUgPSBsaXN0KHhheGlzID0gbGlzdCh0aXRsZSA9ICdHRFAgcGVyIGNhcGl0YScsDQogICAgICAgICAgICAgICAgICAgICAgZ3JpZGNvbG9yID0gJ3JnYigyNTUsIDI1NSwgMjU1KScsDQogICAgICAgICAgICAgICAgICAgICAgemVyb2xpbmV3aWR0aCA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgdGlja2xlbiA9IDUsDQogICAgICAgICAgICAgICAgICAgICAgZ3JpZHdpZHRoID0gMiksDQogICAgICAgICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAnRW5lcmd5IENvbnN1bXB0aW9uJywNCiAgICAgICAgICAgICAgICAgICAgICBncmlkY29sb3IgPSAncmdiKDI1NSwgMjU1LCAyNTUpJywNCiAgICAgICAgICAgICAgICAgICAgICB6ZXJvbGluZXdpZHRoID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICB0aWNrbGVuID0gNSwNCiAgICAgICAgICAgICAgICAgICAgICBncmlkd2l0aCA9IDIpLA0KICAgICAgICAgICAgICAgemF4aXMgPSBsaXN0KHRpdGxlID0gJ0NPMiBFbWlzc2lvbnMnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyaWRjb2xvciA9ICdyZ2IoMjU1LCAyNTUsIDI1NSknLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHplcm9saW5ld2lkdGggPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpY2tsZW4gPSA1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyaWR3aXRoID0gMikpLA0KICAgICAgICAgcGFwZXJfYmdjb2xvciA9ICdyZ2IoMjQzLCAyNDMsIDI0MyknLA0KICAgICAgICAgcGxvdF9iZ2NvbG9yID0gJ3JnYigyNDMsIDI0MywgMjQzKScpDQpgYGANCg0KDQoNCg0KYGBge3J9DQpjbzJfOTV0bzE1IDwtIGVuZXJneV9hbmRfZW1pc3Npb25zICU+JSANCiAgZHBseXI6OnNlbGVjdChDb3VudHJ5LCBDb250aW5lbnQsIEdEUFBDLCBFUEMsIENPMlBDXzE5OTUsIENPMlBDXzIwMDUsIENPMlBDXzIwMTUpICU+JSAjIEZpcnN0IHNlbGVjdCByZWxldmFudCB2YXJpYWJsZXMNCiAgZ2F0aGVyKFllYXIsIENPMiwgLWMoQ291bnRyeSwgQ29udGluZW50LCBHRFBQQywgRVBDKSkgJT4lICMgR2F0aGVyIENPMiBjb2x1bW5zOiBpdCBpcyBpbXBvcnRhbnQgdG8gZXhjbHVkZSBmcm9tIHRoaXMgb3BlcmF0aW9uIHRoZSBjb2x1bW5zIENvdW50cnkgYW5kIEdEUA0KICBtdXRhdGUoWWVhciA9IGZhY3RvcihZZWFyLCANCiAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygiQ08yUENfMTk5NSIsICJDTzJQQ18yMDA1IiwgIkNPMlBDXzIwMTUiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiMTk5NSIsICIyMDA1IiwgIjIwMTUiKSkpICMgUmVsYWJlbCB0aGUgeWVhcnMNCmBgYA0KDQoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnBsb3RfbHkoY28yXzk1dG8xNSwgeCA9IH5HRFBQQywgeSA9IH5FUEMsIHogPSB+Q08yLCANCiAgICAgICAgY29sb3IgPSB+Q29udGluZW50LCANCiAgICAgICAgc2l6ZSA9IH5DTzIsIA0KICAgICAgICBjb2xvcnMgPSBjb2xvcnMsDQogICAgICAgIGZyYW1lID0gflllYXIsDQogICAgICAgIG1hcmtlciA9IGxpc3Qoc3ltYm9sID0gJ2NpcmNsZScsIHNpemVtb2RlID0gJ2RpYW1ldGVyJywNCiAgICAgICAgICAgICAgICAgICAgICBsaW5lID0gbGlzdCh3aWR0aCA9IDIsIGNvbG9yID0gJyMwMDAwMDAnKSksIA0KICAgICAgICAgICAgICAgICAgICAgIHNpemVzID0gYyg1LCA0MCksDQogICAgICAgIHRleHQgPSB+cGFzdGUoJ0NvdW50cnk6JywgQ291bnRyeSwgDQogICAgICAgICAgICAgICAgICAgICAgJzxicj5HRFAgcGVyIGNhcGl0YTonLCByb3VuZChHRFBQQywgMyksIA0KICAgICAgICAgICAgICAgICAgICAgICc8YnI+RW5lcmd5IENvbnN1bXB0aW9uOicsIHJvdW5kKEVQQywzKSwNCiAgICAgICAgICAgICAgICAgICAgICAnPGJyPkNPMiBlbW1pc3Npb25zIDE5OTU6JywgIHJvdW5kKENPMlBDXzE5OTUsIDMpKSkgJT4lDQogIGxheW91dCh0aXRsZSA9ICdHRFAgcGVyIGNhcGl0YSB2LiBFbmVyZ3kgY29uc3VtcHRpb24gcGVyIGNhcGl0YScsDQogICAgICAgICBzY2VuZSA9IGxpc3QoeGF4aXMgPSBsaXN0KHRpdGxlID0gJ0dEUCBwZXIgY2FwaXRhJywNCiAgICAgICAgICAgICAgICAgICAgICBncmlkY29sb3IgPSAncmdiKDI1NSwgMjU1LCAyNTUpJywNCiAgICAgICAgICAgICAgICAgICAgICB6ZXJvbGluZXdpZHRoID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICB0aWNrbGVuID0gNSwNCiAgICAgICAgICAgICAgICAgICAgICBncmlkd2lkdGggPSAyKSwNCiAgICAgICAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICdFbmVyZ3kgQ29uc3VtcHRpb24nLA0KICAgICAgICAgICAgICAgICAgICAgIGdyaWRjb2xvciA9ICdyZ2IoMjU1LCAyNTUsIDI1NSknLA0KICAgICAgICAgICAgICAgICAgICAgIHplcm9saW5ld2lkdGggPSAxLA0KICAgICAgICAgICAgICAgICAgICAgIHRpY2tsZW4gPSA1LA0KICAgICAgICAgICAgICAgICAgICAgIGdyaWR3aXRoID0gMiksDQogICAgICAgICAgICAgICB6YXhpcyA9IGxpc3QodGl0bGUgPSAnQ08yIEVtaXNzaW9ucycsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JpZGNvbG9yID0gJ3JnYigyNTUsIDI1NSwgMjU1KScsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmFuZ2UgPSBjKDAsIDAuMSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgemVyb2xpbmV3aWR0aCA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgdGlja2xlbiA9IDUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JpZHdpdGggPSAyKSksDQogICAgICAgICBwYXBlcl9iZ2NvbG9yID0gJ3JnYigyNDMsIDI0MywgMjQzKScsDQogICAgICAgICBwbG90X2JnY29sb3IgPSAncmdiKDI0MywgMjQzLCAyNDMpJykgJT4lIA0KICBhbmltYXRpb25fb3B0cygNCiAgICAxMzAwLCByZWRyYXcgPSBGQUxTRQ0KICApDQpgYGANCg==